/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.forge.validation;
import java.io.FileNotFoundException;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.jboss.forge.parser.java.Annotation;
import org.jboss.forge.parser.java.Field;
import org.jboss.forge.parser.java.JavaClass;
import org.jboss.forge.parser.java.Member;
import org.jboss.forge.parser.java.Method;
import org.jboss.forge.project.Project;
import org.jboss.forge.project.facets.JavaSourceFacet;
import org.jboss.forge.resources.Resource;
import org.jboss.forge.resources.java.JavaResource;
import org.jboss.forge.shell.Shell;
import org.jboss.forge.shell.plugins.Alias;
import org.jboss.forge.shell.plugins.Command;
import org.jboss.forge.shell.plugins.Option;
import org.jboss.forge.shell.plugins.PipeOut;
import org.jboss.forge.shell.plugins.Plugin;
import org.jboss.forge.shell.plugins.RequiresFacet;
import org.jboss.forge.shell.plugins.RequiresResource;
import org.jboss.forge.validation.api.ValidationFacet;
import org.jboss.forge.validation.completer.PropertyCompleter;
import org.jboss.forge.validation.util.JavaHelper;
import static org.jboss.forge.shell.PromptType.JAVA_CLASS;
import static org.jboss.forge.validation.util.ResourceHelper.getJavaClassFromResource;
/**
* @author Kevin Pollet
*/
@Alias("add-constraint")
@RequiresResource({JavaResource.class})
@RequiresFacet({ValidationFacet.class, JavaSourceFacet.class})
public class PropertyConstraintPlugin implements Plugin
{
private final JavaSourceFacet javaSourceFacet;
private final Shell shell;
@Inject
public PropertyConstraintPlugin(Project project, Shell shell)
{
this.javaSourceFacet = project.getFacet(JavaSourceFacet.class);
this.shell = shell;
}
@Command(value = "Valid", help = "Adds @Valid constraint on the specified property")
public void addValidConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraint = addConstraintOnProperty(property, onAccessor, Valid.class);
javaSourceFacet.saveJavaSource(constraint.getOrigin());
outputConstraintAdded(pipeOut, property, Valid.class);
}
@Command(value = "Null", help = "Adds @Null constraint on the specified property")
public void addNullConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraint = addConstraintOnProperty(property, onAccessor, Null.class);
setConstraintMessage(constraint, message);
javaSourceFacet.saveJavaSource(constraint.getOrigin());
outputConstraintAdded(pipeOut, property, Null.class);
}
@Command(value = "NotNull", help = "Adds @NotNull constraint on the specified property")
public void addNotNullConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, NotNull.class);
setConstraintMessage(constraintAnnotation, message);
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, NotNull.class);
}
@Command(value = "AssertTrue", help = "Adds @AssertTrue constraint on the specified property")
public void addAssertTrueConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, AssertTrue.class);
setConstraintMessage(constraintAnnotation, message);
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, AssertTrue.class);
}
@Command(value = "AssertFalse", help = "Adds @AssertFalse constraint on the specified property")
public void addAssertFalseConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, AssertFalse.class);
setConstraintMessage(constraintAnnotation, message);
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, AssertFalse.class);
}
@Command(value = "Min", help = "Adds @Min constraint on the specified property")
public void addMinConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "min", required = true) long min,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, Min.class);
setConstraintMessage(constraintAnnotation, message);
constraintAnnotation.setLiteralValue(String.valueOf(min));
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, Min.class);
}
@Command(value = "Max", help = "Adds @Max constraint on the specified property")
public void addMaxConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "max", required = true) long max,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, Max.class);
setConstraintMessage(constraintAnnotation, message);
constraintAnnotation.setLiteralValue(String.valueOf(max));
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, Max.class);
}
@Command(value = "DecimalMin", help = "Adds @DecimalMin constraint on the specified property")
public void addDecimalMinConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "min", required = true) String min,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, DecimalMin.class);
setConstraintMessage(constraintAnnotation, message);
constraintAnnotation.setStringValue(min);
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, DecimalMin.class);
}
@Command(value = "DecimalMax", help = "Adds @DecimalMax constraint on the specified property")
public void addDecimalMaxConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "max", required = true) String max,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, DecimalMax.class);
setConstraintMessage(constraintAnnotation, message);
constraintAnnotation.setStringValue(max);
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, DecimalMax.class);
}
@Command(value = "Size", help = "Adds @Size constraint on the specified property")
public void addSizeConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "min") Integer min,
@Option(name = "max") Integer max,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, Size.class);
setConstraintMessage(constraintAnnotation, message);
if (min != null)
{
constraintAnnotation.setLiteralValue("min", String.valueOf(min));
}
if (max != null)
{
constraintAnnotation.setLiteralValue("max", String.valueOf(max));
}
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, Size.class);
}
@Command(value = "Digits", help = "Adds @Digits constraint on the specified property")
public void addDigitsConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "integer") int integer,
@Option(name = "fraction") int fraction,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, Digits.class);
setConstraintMessage(constraintAnnotation, message);
constraintAnnotation.setLiteralValue("integer", String.valueOf(integer));
constraintAnnotation.setLiteralValue("fraction", String.valueOf(fraction));
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, Digits.class);
}
@Command(value = "Past", help = "Adds @Past constraint on the specified property")
public void addPastConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, Past.class);
setConstraintMessage(constraintAnnotation, message);
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, Past.class);
}
@Command(value = "Future", help = "Adds @Future constraint on the specified property")
public void addFutureConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, Future.class);
setConstraintMessage(constraintAnnotation, message);
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, Future.class);
}
@Command(value = "Pattern", help = "Adds @Pattern constraint on the specified property")
public void addPatternConstraint(@Option(name = "onProperty", completer = PropertyCompleter.class, required = true) String property,
@Option(name = "onAccessor", flagOnly = true) boolean onAccessor,
@Option(name = "regexp", required = true) String regexp,
@Option(name = "flags") Pattern.Flag[] flags,
@Option(name = "message") String message,
@Option(name = "groups", type = JAVA_CLASS) String[] groups,
PipeOut pipeOut) throws FileNotFoundException
{
final Annotation<JavaClass> constraintAnnotation = addConstraintOnProperty(property, onAccessor, Pattern.class);
setConstraintMessage(constraintAnnotation, message);
constraintAnnotation.setStringValue("regexp", regexp);
constraintAnnotation.getOrigin().addImport(Pattern.Flag.class);
final StringBuilder flagsLiteral = new StringBuilder();
flagsLiteral.append('{');
if (flags != null)
{
int i = 0;
for (Pattern.Flag oneFlag : flags)
{
flagsLiteral.append(oneFlag);
if (i < (flags.length - 1))
{
flagsLiteral.append(",");
}
i++;
}
}
flagsLiteral.append('}');
constraintAnnotation.setStringValue("flags", flagsLiteral.toString());
javaSourceFacet.saveJavaSource(constraintAnnotation.getOrigin());
outputConstraintAdded(pipeOut, property, Pattern.class);
}
private Annotation<JavaClass> addConstraintOnProperty(String property, boolean onAccessor, Class<? extends java.lang.annotation.Annotation> annotationClass)
throws FileNotFoundException
{
final Resource<?> currentResource = shell.getCurrentResource();
final JavaClass clazz = getJavaClassFromResource(currentResource);
final Field<JavaClass> field = clazz.getField(property);
if (field == null)
{
throw new IllegalStateException("The current class has no property named '" + property + "'");
}
Member<JavaClass, ?> member = field;
if (onAccessor)
{
final Method<JavaClass> accessor = JavaHelper.getFieldAccessor(field);
if (accessor == null)
{
throw new IllegalStateException("The property named '" + property + "' has no accessor");
}
member = accessor;
}
if (member.hasAnnotation(annotationClass))
{
throw new IllegalStateException("The element '" + member.getName() + "' is already annotated with @" + annotationClass.getSimpleName());
}
return member.addAnnotation(annotationClass);
}
private void setConstraintMessage(Annotation<JavaClass> annotation, String message)
{
if (message != null)
{
annotation.setStringValue("message", message);
}
}
private void outputConstraintAdded(PipeOut pipeOut, String property, Class<? extends java.lang.annotation.Annotation> constraintClass)
{
pipeOut.println("Constraint " + constraintClass.getSimpleName() + " has been successfully added on property named '" + property + "'");
}
}